package contower

import (
	"context"
	"encoding/json"
	"fmt"
	"github.com/shopspring/decimal"
	"go.uber.org/zap"
	"google.golang.org/protobuf/proto"
	"strconv"
	"strings"
	"sync"
	"time"
	"wzgames/internal/codec/sfs"
	"wzgames/internal/constant"
	"wzgames/internal/gdao/aviatordao"
	"wzgames/internal/gloader"
	"wzgames/internal/model/aviatorp"
	"wzgames/internal/model/gamep"
	"wzgames/internal/model/user"
	"wzgames/internal/response"
	"wzgames/pkg/actor/actor"
	"wzgames/pkg/actor/actorservice"
	"wzgames/pkg/common/timingwheel"
	"wzgames/pkg/components/mysql"
	"wzgames/pkg/components/redis"
	"wzgames/pkg/logger"
	"wzgames/pkg/utils/stringutil"
)

// 小飞机 投注 CashOut统计

type AirBetMgr struct {
	mu     sync.RWMutex
	pid    *actor.PID
	instId int64
	ds     *mysql.DS
	dao    *aviatordao.AviatorDao
	redisC *redis.RedisClient

	// first 3 bets 统计前3个投注
	fmap  map[uint64]bool
	farr  []BetU
	count int

	bets       []AviatorBet // 当前轮投注列表
	cashouts   []AviatorBet // 当前轮CashOut列表
	betpos     int
	cashoutpos int
	betKey     string
	cashOutKey string

	betCount  int64
	cashCount int64

	t1 *timingwheel.Timer // 定时推送投注玩家给客户端
	t2 *timingwheel.Timer // 定时推送CashOut玩家给客户端
}

func newAirBetMgr(instId int64) *AirBetMgr {
	s := strconv.FormatInt(instId, 10)
	ab := &AirBetMgr{
		instId:     instId,
		fmap:       make(map[uint64]bool),
		count:      0,
		betKey:     constant.AvtBetsNKey + "." + s,
		cashOutKey: constant.AvtBetCashOutsNKey + "." + s,
		redisC:     gloader.CommMgr().RedisPool(),
	}

	name := fmt.Sprintf("aircraft-ab-%s", strconv.FormatInt(instId, 10))
	props := actor.PropsFromProducer(func() actor.Actor { return ab })
	pid, err := actorservice.ActorSystem().Root.SpawnNamed(props, name)
	if err != nil {
		logger.Error("[ConTower] spawn air bet manager actor error", zap.Int64("InstId", instId), zap.Error(err))
		return nil
	}
	ab.pid = pid
	ab.ds = gloader.CommMgr().GetDBProxy(constant.MysqlOfAviator)
	ab.dao = &aviatordao.AviatorDao{Driver: ab.ds.Ds()}

	return ab
}

func (ab *AirBetMgr) Pid() *actor.PID {
	return ab.pid
}

func (ab *AirBetMgr) Receive(ctx actor.Context) {
	switch m := ctx.Message().(type) {
	case *aviatorp.BetNote:
		switch m.Op {
		case constant.AvtBetBet: // 小飞机投注
			ab.processBetNote(m)
		case constant.AvtBetCancel: // 小飞机取消投注
			ab.processBetCancel(m)
		case constant.AvtBetCashOut: // 小飞机CashOut
			ab.processCashOutNote(m)
		}
	case *aviatorp.AvtRoundAct:
		ab.processRoundAct(m)
	default:
	}
}

func (ab *AirBetMgr) Clean() {
	ab.mu.Lock()
	ab.fmap = make(map[uint64]bool)
	ab.farr = ab.farr[0:0]
	ab.count = 0
	ab.mu.Unlock()
}

// PrepareForNextRound 新一轮准备开始，清空旧数据
func (ab *AirBetMgr) PrepareForNextRound() {
	ab.StopBetsInfoTimer()
	ab.StopCashoutsTimer()
	ab.bets = ab.bets[0:0]
	ab.cashouts = ab.cashouts[0:0]
	ab.betpos = 0
	ab.cashoutpos = 0
	ab.cashCount = 0
	ab.betCount = 0
}

func (ab *AirBetMgr) StopBetsInfoTimer() {
	if ab.t1 != nil {
		ab.t1.Stop()
		ab.t1 = nil
	}
}

func (ab *AirBetMgr) StopCashoutsTimer() {
	if ab.t2 != nil {
		ab.t2.Stop()
		ab.t2 = nil
	}
}

func (ab *AirBetMgr) processBetNote(data *aviatorp.BetNote) {
	defer logger.Recover()
	ab.mu.Lock()
	if ab.count < 3 { // 前3个投注的用于计算小飞机Crash值
		if _, ok := ab.fmap[data.BetU.Uid]; !ok {
			ab.count++
			ab.fmap[data.BetU.Uid] = true
			ab.farr = append(ab.farr, BetU{
				AgtId:    data.BetU.AgtId,
				AgtKey:   data.BetU.AgtKey,
				Uid:      data.BetU.Uid,
				Uname:    data.BetU.Uname,
				Seed:     data.ClientSeed,
				Uhead:    data.BetU.Uhead,
				Currency: data.BetU.Currency,
			})
		}
	}
	ab.mu.Unlock()
	_bet, _ := stringutil.ToFloat64(data.BetStr, 0)
	_betid, _ := stringutil.ToInt32(data.BetId, 1)
	ab.bets = append(ab.bets, AviatorBet{
		AgtId:    data.BetU.AgtId,
		Uid:      data.BetU.Uid,
		AgtKey:   data.BetU.AgtKey,
		Uname:    data.BetU.Uname,
		Uhead:    data.BetU.Uhead,
		Currency: data.BetU.Currency,
		BetAmt:   _bet,
		BetId:    _betid,
		FreeBet:  data.FreeBet,
	})
	ab.betCount++
	response.ReleaseAvtBet(data)

	//logger.Info("processBetNote", zap.Int32("BetId", _betid))
}

func (ab *AirBetMgr) getFarrLen() int {
	return len(ab.farr)
}

func (ab *AirBetMgr) fillFarr(data *user.UserEntity, clientSeed string) {
	if len(ab.farr) < 3 {
		ab.farr = append(ab.farr, BetU{
			AgtId: data.AgtId,
			Uid:   data.Uid,
			Uname: data.Uname,
			Seed:  clientSeed,
			Uhead: data.Uhead,
		})
	} else {
		ab.farr[2] = BetU{
			AgtId: data.AgtId,
			Uid:   data.Uid,
			Uname: data.Uname,
			Seed:  clientSeed,
			Uhead: data.Uhead,
		}
	}
}

func (ab *AirBetMgr) processCashOutNote(data *aviatorp.BetNote) {
	defer logger.Recover()
	//logger.Info("processCashOutNote", zap.Int32("Op", data.Op))

	_bet, _ := stringutil.ToFloat64(data.BetStr, 0)
	_betid, _ := stringutil.ToInt32(data.BetId, 1)

	ab.cashouts = append(ab.cashouts, AviatorBet{
		AgtId:      data.BetU.AgtId,
		Uid:        data.BetU.Uid,
		AgtKey:     data.BetU.AgtKey,
		Uname:      data.BetU.Uname,
		Uhead:      data.BetU.Uhead,
		Currency:   data.BetU.Currency,
		BetAmt:     _bet,
		BetId:      _betid,
		Multiplier: data.Multiplier,
		WinAmt:     data.WinAmt,
	})
	ab.cashCount++
}

func (ab *AirBetMgr) processBetCancel(data *aviatorp.BetNote) {
	defer logger.Recover()
	//logger.Info("processBetCancel", zap.Int32("Op", data.Op))

	_betid, _ := stringutil.ToInt32(data.BetId, 1)
	for i, v := range ab.bets {
		if v.AgtId == data.BetU.AgtId && v.Uid == data.BetU.Uid && v.BetId == _betid {
			aL := len(ab.bets)
			if i < aL-1 {
				ab.bets = append(ab.bets[:i], ab.bets[i+1:]...)
			} else {
				ab.bets = ab.bets[:aL-1]
			}
			break
		}
	}
}

// 结算该局利润到总水池和奖池中
func (ab *AirBetMgr) GetAllProfitValue() decimal.Decimal {
	allBetVal := 0.0
	allCashOutVal := 0.0
	for _, v := range ab.bets {
		allBetVal += v.BetAmt
	}
	for _, v := range ab.cashouts {
		allCashOutVal += v.WinAmt
	}
	//logger.Info("all bet all cash out", zap.Float64("bet", allBetVal), zap.Float64("profit", allCashOutVal))
	return decimal.NewFromFloat(allBetVal - allCashOutVal)
}

func (ab *AirBetMgr) UpdatePoolsValue(totalPool int64, rewardPool int64) {
	//logger.Info("pools", zap.Int64("total pool", totalPool), zap.Int64("reward pool", rewardPool))

	//将总水池奖池值存入redis
	poolsValue := aviatorp.AviatorPoolsValue{
		TotalPoolValue:  totalPool,
		RewardPoolValue: rewardPool,
	}
	b, err := json.Marshal(poolsValue)
	if err != nil {
		logger.Error("[ConTower] json Marshal pools value failed", zap.Error(err))
		return
	}
	ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout)
	_, err = ab.redisC.Get().Set(ctx, "aviator_pools_value", b, 0).Result()
	cancel()
	if err != nil {
		logger.Error("[ConTower] save pools value failed", zap.Error(err))
	}
}

// First3Bets 前3个投注的
func (ab *AirBetMgr) First3Bets() (*aviatorp.First3Bets, string) {
	first3 := &aviatorp.First3Bets{}
	var combineStr strings.Builder
	for _, nd := range ab.farr {
		first3.First3 = append(first3.First3, &gamep.BetUser{
			AgtId:    nd.AgtId,
			AgtKey:   nd.AgtKey,
			Uid:      nd.Uid,
			Uname:    nd.Uname,
			Seed:     nd.Seed,
			Uhead:    nd.Uhead,
			Currency: nd.Currency,
		})
		combineStr.WriteString(nd.Seed)
	}
	return first3, combineStr.String()
}

func (ab *AirBetMgr) RoundBetCount() int64 {
	return ab.betCount
}

func (ab *AirBetMgr) RoundCashCount() int64 {
	return ab.cashCount
}

// BetsNotify 批量投注播报
func (ab *AirBetMgr) BetsNotify() []byte {
	ret := &gamep.BetsNotify{Tp: constant.AvtBetBet}
	l := len(ab.bets)
	var c int
	var data []AviatorBet
	if l-ab.betpos >= 50 {
		c = 50
		end := ab.betpos + 50
		data = ab.bets[ab.betpos:end]
		ab.betpos = end
	} else {
		if ab.betpos < l {
			c = l - ab.betpos
			data = ab.bets[ab.betpos:l]
			ab.betpos = l
		}
	}

	if c > 0 {
		ret.Count = int32(l)
		xw := sfs.XWriter{}
		defer xw.Reset()
		xw.WriteUShort(uint16(2))
		xw.WriteStringS(sfs.CmdKey, constant.CmdUpdateCurrentBets)
		xw.WriteSfsObjectPre(sfs.ParamId, uint16(3))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteIntS(constant.KeyBetsCount, ret.Count)
		xw.WriteSfsArrayPre(constant.KeyBets, uint16(len(data)))
		for _, bt := range data {
			xw.WriteUByte(sfs.SfsObject)
			xw.WriteUShort(uint16(7))
			xw.WriteDoubleS(constant.KeyBet, bt.BetAmt)
			xw.WriteIntS(constant.KeyBetId, bt.BetId)
			xw.WriteStringS(constant.KeyCurrency, bt.Currency)
			xw.WriteBoolS(constant.KeyIsFreeBet, bt.FreeBet)
			xw.WriteStringS(constant.KeyPlayerid, fmt.Sprintf("%v_%s", bt.Uid, bt.AgtKey))
			xw.WriteStringS(constant.KeyUsername, fmt.Sprintf("%s_%s", bt.AgtKey, bt.Uname))
			xw.WriteStringS(constant.KeyProfileImage, bt.Uhead)
		}
		ret.Data = append(ret.Data, xw.Data()...)
		b, err := proto.Marshal(ret)
		if err != nil {
			logger.Error("[ConTower] bets notify fail on marshal", zap.Int64("InstId", ab.instId), zap.Error(err))
			return nil
		}

		return b
	}

	return nil
}

// CashoutsNotify 批量CashOut播报
func (ab *AirBetMgr) CashoutsNotify() []byte {
	ret := &gamep.BetsNotify{Tp: constant.AvtBetCashOut}
	l := len(ab.cashouts)
	var c int
	var data []AviatorBet
	if l-ab.cashoutpos >= 50 {
		end := ab.cashoutpos + 50
		data = ab.cashouts[ab.cashoutpos:end]
		ab.cashoutpos = end
		c = 50
	} else {
		if ab.cashoutpos < l {
			c = l - ab.cashoutpos
			data = ab.cashouts[ab.cashoutpos:l]
			ab.cashoutpos = l
		}
	}

	if c > 0 {
		ret.Count = int32(l)
		xw := sfs.XWriter{}
		defer xw.Reset()
		xw.WriteUShort(uint16(2))
		xw.WriteStringS(sfs.CmdKey, constant.CmdUpdateCurrentCashOuts)
		xw.WriteSfsObjectPre(sfs.ParamId, uint16(2))
		xw.WriteShortS(sfs.KeyCode, response.CSuccess)
		xw.WriteSfsArrayPre(constant.KeyCashouts, uint16(len(data)))
		for _, bt := range data {
			xw.WriteUByte(sfs.SfsObject)
			xw.WriteUShort(uint16(7))
			xw.WriteDoubleS(constant.KeyBet, bt.BetAmt)
			xw.WriteIntS(constant.KeyBetId, bt.BetId)
			xw.WriteStringS(constant.KeyCurrency, bt.Currency)
			xw.WriteStringS(constant.KeyPlayerid, fmt.Sprintf("%v_%s", bt.Uid, bt.AgtKey))
			xw.WriteStringS(constant.KeyUsername, fmt.Sprintf("%s_%s", bt.AgtKey, bt.Uname))
			xw.WriteDoubleS(constant.KeyWinAmount, bt.WinAmt)
			xw.WriteDoubleS(constant.KeyMultiplier, bt.Multiplier)
		}
		ret.Data = append(ret.Data, xw.Data()...)
		b, err := proto.Marshal(ret)
		if err != nil {
			logger.Error("[ConTower] cashouts notify fail on marshal", zap.Int64("InstId", ab.instId), zap.Error(err))
			return nil
		}

		return b
	}

	return nil
}

func (ab *AirBetMgr) BroadcastBetsInfo(data interface{}) {
	betsInfo := ab.BetsNotify()
	if betsInfo != nil {
		_ = gloader.CommMgr().NatsC().Publish(ab.betKey, betsInfo)
	}

	ab.t1 = TimerAfter(time.Second, ab.BroadcastBetsInfo, nil)
}

func (ab *AirBetMgr) BroadcastCashoutsInfo(data interface{}) {
	cashoutInfo := ab.CashoutsNotify()
	if cashoutInfo != nil {
		_ = gloader.CommMgr().NatsC().Publish(ab.cashOutKey, cashoutInfo)
	}

	ab.t2 = TimerAfter(time.Second, ab.BroadcastCashoutsInfo, nil)
}

// processRoundAct 玩家投注动作记录
func (ab *AirBetMgr) processRoundAct(data *aviatorp.AvtRoundAct) {
	ent := &aviatorp.AvtRoundActEntity{
		BetId:    data.BetId,
		RoundId:  data.RoundId,
		Dateline: data.Dateline,
		Uid:      data.Uid,
		Idx:      data.Idx,
		Op:       data.Op,
		Result:   data.Result,
		Payout:   data.Payout,
	}
	_ = ab.dao.CreateAvtRoundAct(ent)
}
